using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using PiggyBankCashFlowApi.Exception;

namespace PiggyBankCashFlowApi.Middleware;

public class GlobalExceptionHandlerMiddleware(ILogger<GlobalExceptionHandlerMiddleware> logger, RequestDelegate next)

{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (ArgumentException ex)
        {
            logger.LogError(ex, "Wrong arguments for {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Not Accepted",
                StatusCodes.Status406NotAcceptable,
                ex.Message);
        }
        catch (SqlException ex)
        {
            logger.LogError(ex, "Unknown Sql error occurs when executing {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(context,
                "Conflict",
                StatusCodes.Status409Conflict,
                "Sql exception, please contact server manager.");
        }
        catch (DbOpNotAcceptedException ex)
        {
            logger.LogWarning(ex, "Sql operation no accepted when executing {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Not Accepted",
                StatusCodes.Status406NotAcceptable,
                ex.Message);
        }
        catch (ResourceNotFoundException ex)
        {
            logger.LogError(ex, "Db resource no found when executing {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Not Found",
                StatusCodes.Status404NotFound,
                ex.Message);
        }
        catch (JwtRoleException ex)
        {
            logger.LogError(ex, "Jwt token element : role is found missing when executing {method} {path}",
                context.Request.Method, context.Request.Path);
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await ResponseWithProblemDetailsAsync(
                context,
                "Unauthorized",
                statusCode: StatusCodes.Status401Unauthorized,
                ex.Message);
        }
        catch (JwtNameIdentifierException ex)
        {
            logger.LogError(ex, "Jwt token element : Id is found missing when executing {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Unauthorized",
                statusCode: StatusCodes.Status401Unauthorized,
                ex.Message);
        }
        catch (ForbiddenException ex)
        {
            logger.LogError(ex, "Operation forbidden for {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Forbidden",
                StatusCodes.Status403Forbidden,
                ex.Message);
        }
        catch (System.Exception ex)
        {
            logger.LogError(ex, "Unhandled exception occurred when executing request {method} {path}",
                context.Request.Method, context.Request.Path);
            await ResponseWithProblemDetailsAsync(
                context,
                "Internal server error",
                StatusCodes.Status500InternalServerError,
                "Unhandled exception occurred.");
        }
    }

    private async Task ResponseWithProblemDetailsAsync(HttpContext context,string problemDetailTitle, int statusCode,string message)
    {
        var problemDetails = new ProblemDetails()
        {
            Status = statusCode,
            Title = problemDetailTitle,
            Detail = message,
            Type = $"https://httpstatuses.com/{statusCode}",
            Instance = context.Request.Path.ToString()
        };
        context.Response.StatusCode = statusCode;
        context.Response.ContentType = "application/problem+json";
        await context.Response.WriteAsJsonAsync(problemDetails);
    }
}